%%%---------------------------------------------------------------------------------------
%%% @author    Stuart Jackson <sjackson@simpleenigma.com> [http://erlsoft.org]
%%% @copyright 2006 - 2008 Simple Enigma, Inc. All Rights Reserved.
%%% @doc       Simple Storage Solution Libraray
%%% @reference See <a href="http://erlsoft.org/modules/eifaws" target="_top">Erlang Software Framework</a> for more information
%%% @reference See <a href="http://eifaws.googlecode.com" target="_top">ErlMail Google Code Repository</a> for more information
%%% @version   0.0.1
%%% @since     0.0.1
%%% @end
%%%
%%%
%%% The MIT License
%%%
%%% Copyright (c) 2008 Stuart Jackson, Simple Enigma, Inc. All Righs Reserved
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%%% THE SOFTWARE.
%%%
%%%
%%%---------------------------------------------------------------------------------------

-module(eifaws_s3).
-author('sjackson@simpleenigma.com').
-include("../include/eifaws.hrl").


-compile(export_all).
get(AWSInfo) -> 
	S3Command = #s3_command{method = get, headers = [s3date()]},
	command(AWSInfo,S3Command).

put_bucket(AWSInfo,Bucket) ->
	S3Command = #s3_command{method = put, uri = "/", bucket = Bucket, headers = [s3date(),{"content-length",0}]},
	command(AWSInfo,S3Command).
put_bucket(AWSInfo,Bucket,Location) when Location == "EU"; Location == "US" ->
	Body = "<CreateBucketConfiguration><LocationConstraint>" ++ Location ++ "</LocationConstraint></CreateBucketConfiguration>",
	S3Command = #s3_command{method = put, uri = "/", bucket = Bucket, headers = [s3date(),{"content-length",length(Body)}], body = Body},
	command(AWSInfo,S3Command).
get_bucket(AWSInfo,Bucket) ->
	S3Command = #s3_command{method = get, headers = [s3date()], bucket = Bucket},
	command(AWSInfo,S3Command).
get_bucket(AWSInfo,Bucket,_Prefix,_Marker,_Delimiter,_MaxKeys) ->
	S3Command = #s3_command{method = get, headers = [{"date","Tue, 27 Mar 2007 19:42:41 +0000"}], bucket = Bucket},
	command(AWSInfo,S3Command).
get_bucket_location(AWSInfo,Bucket) -> 
	S3Command = #s3_command{method = get, uri="/", sub_resource="?location", bucket = Bucket, headers = [s3date()]},
	command(AWSInfo,S3Command).
delete_bucket(AWSInfo,Bucket) ->
	S3Command = #s3_command{method = delete, uri = "/", bucket = Bucket, headers = [s3date()]},
	command(AWSInfo,S3Command).

post_object() -> ok.

put_object(AWSInfo,Bucket,URI,Body) ->
	S3Command = #s3_command{method = put, uri = URI, bucket = Bucket, body = Body,
		headers = [s3date(),{"content-length",length(Body)}]},
	command(AWSInfo,S3Command).

copy_object(AWSInfo,SrcBucket,SrcURI,DstBucket,DstURI) ->
	S3Command = #s3_command{method = put, uri = DstURI, bucket = DstBucket,
		headers = [s3date()], amazon_headers = [{'x-amz-copy-source',"/" ++ SrcBucket ++ SrcURI}]},

	command(AWSInfo,S3Command).

get_object(AWSInfo,Bucket,URI) ->
	S3Command = #s3_command{method = get, uri = URI, headers = [s3date()], bucket = Bucket},
	command(AWSInfo,S3Command).

head_object(AWSInfo,Bucket,URI) ->
	S3Command = #s3_command{method = head, uri = URI, headers = [s3date()], bucket = Bucket},
	command(AWSInfo,S3Command).


delete_object(AWSInfo,Bucket,URI) ->
	S3Command = #s3_command{method = delete, uri = URI, headers = [s3date()], bucket = Bucket},
	command(AWSInfo,S3Command).






command(Atom,S3Command) when is_atom(Atom) -> command(eifaws_util:get_account(Atom),S3Command);
command(AWSInfo,S3Command) when is_record(AWSInfo,aws_info) -> 
	Signature = signature(S3Command,AWSInfo),
	Auth = auth(AWSInfo#aws_info.aws_access_key, Signature),
	Headers = lists:append(S3Command#s3_command.headers,[Auth]),
	Host = host_name(S3Command),
%	http:request(S3Command#s3_command.method,Request,[],[]).
	URL = "http://" ++ Host ++ S3Command#s3_command.uri ++ S3Command#s3_command.uquery ++ S3Command#s3_command.sub_resource,
	?D(S3Command),
	Request = case S3Command#s3_command.method of
		get -> {URL,Headers};
		put -> {URL,Headers,[],S3Command#s3_command.body};
		_ -> {URL,Headers}
	end,
	?D(Request),
	http:request(S3Command#s3_command.method,Request,[],[]).


host_name(S3Command) ->
	case S3Command#s3_command.bucket of
		[] -> ?AWS_S3_SERVER;
		_Bucket -> S3Command#s3_command.bucket ++ "." ++ ?AWS_S3_SERVER
	end.



signature(S3Command,AWSInfo) -> 
	Verb = string:to_upper(atom_to_list(S3Command#s3_command.method)),
	case lists:keysearch("Content-MD5",1,S3Command#s3_command.headers) of
		{value,{_,ContentMD5}} -> ok;
		_ -> ContentMD5 = []
	end,
	case lists:keysearch("date",1,S3Command#s3_command.headers) of
		{value,{_,D}} when is_tuple(D) -> Date = rfc2616_date(D);
		{value,{_,Date}} -> ok;
		_ -> Date = []
	end,
	AHL = lists:foldr(fun({K,V},Acc) -> [atom_to_list(K) ++ ":" ++ V ++ "\n"|Acc] end, [] , lists:keysort(1,S3Command#s3_command.amazon_headers)),
	Res = resource(S3Command),
	StringToSign = case lists:keymember('x-amz-date',1,S3Command#s3_command.amazon_headers) of
		true -> lists:flatten([Verb,10,ContentMD5,10,S3Command#s3_command.content_type,10,[],10,AHL,Res]);
		false -> lists:flatten([Verb,10,ContentMD5,10,S3Command#s3_command.content_type,10,Date,10,AHL,Res])
	end,
	?D(StringToSign),
	SHA1 = binary_to_list(crypto:sha_mac(AWSInfo#aws_info.aws_secret_key_id,StringToSign)),
	binary_to_list(base64:encode(SHA1)).

resource(#s3_command{bucket = Bucket, uri = URI, uquery = UQuery, sub_resource = Sub}) ->
	case {Bucket,URI,UQuery,Sub} of
		{[],[],[],[]}  -> "/";
		{Bucket,[],[],Sub}  -> "/" ++ Bucket ++ "/" ++ Sub;
		{Bucket,URI,[],[]}  -> "/" ++ Bucket ++ URI;
		{Bucket,URI,UQuery,[]} -> "/" ++ Bucket ++ URI ++ UQuery;
		{Bucket,URI,[],Sub} -> "/" ++ Bucket ++ URI ++ Sub
	end.




auth(AWSAccessKeyId,Signature) -> {"Authorization","AWS " ++ AWSAccessKeyId ++ ":" ++ Signature}.


amz_date() -> s3date("x-amz-date",calendar:universal_time()).
amz_date(Date) -> s3date("x-amz-date",Date).

s3date() -> s3date("date",calendar:universal_time()).
s3date(Date) -> s3date("date",Date).
s3date(String,Date) -> {String,rfc2616_date(Date)}.

rfc2616_date({{Year,Month,Day},{Hour,Minute,Sec}}) ->
	DayString = day_string(Year,Month,Day),
	MonthString = month_string(Month),
	Date = io_lib:format("~s, ~w ~s ~w ~2.2.0w:~2.2.0w:~2.2.0w +0000",[DayString,Day,MonthString,Year,Hour,Minute,Sec]),
	lists:flatten(Date).




month_string(Month) ->
	case Month of
		1 -> "Jan";
		2 -> "Feb";
		3 -> "Mar";
		4 -> "Apr";
		5 -> "May";
		6 -> "Jun";
		7 -> "Jul";
		8 -> "Aug";
		9 -> "Sep";
		10 -> "Oct";
		11 -> "Nov";
		12 -> "Dec"
	end.
day_string({Year,Month,Day}) -> day_string(Year,Month,Day).
day_string(Year,Month,Day) ->
	case calendar:day_of_the_week(Year, Month, Day) of
		1 -> "Mon";
		2 -> "Tue";
		3 -> "Wed";
		4 -> "Thu";
		5 -> "Fri";
		6 -> "Sat";
		7 -> "Sun"
	end.

